查看原文
其他

Mysql 的优化方式,都给你整理好了(附思维导图)


前言

MYSQL优化主要分为以下四大方面:
设计:存储引擎,字段类型,范式与逆范式
功能:索引,缓存,分区分表。
架构:主从复制,读写分离,负载均衡。
合理SQL:测试,经验。


思维导图




一、存储引擎


在创建表的时候我们使用sql语句,Create table tableName () engine=myisam|innodb;

这里就指明了存储引擎是myisam还是innodb。存储引擎是一种用来存储MySQL中对象(记录和索引)的一种特定的结构(文件结构),处于MySQL服务器的最底层,直接存储数据。导致上层的操作,依赖于存储引擎的选择。地位如下图:


网络接口层:与客户端通信,比如传输数据等等。存储引擎层:存储数据的规则,方式。

本质:存储引擎就是特定的数据存储格式(方案)。

(1)关于Innodb 和myisam的取舍:
Innodb :数据完整性,并发性处理,擅长更新,删除。
myisam:高速查询及插入。擅长插入和查询。

具体举例:

那么对于微博项目来看,选择哪一个存储引擎呢?

a.微博主要是插入微博和查询微博列表,较为适合MyISAM;

b.微博在更新微博和删除微博,要少的多,较为适合MyISAM;

c.对数据完整性的需求并没有那么强烈,比如用户删除微博,关联的转播和评论并不要求都做相应的行为,较为适合MyISAM;


那么对于记账财务系统,选择哪一款存储引擎呢?

a.财务系统除了读取和插入,经常要进行数据的修改和删除,较为适合InnoDB;

b.在进行财务变更的时候,如果失败需要回滚必须用到事务,较为适合InnoDB;

c.每个用户的财务数据完整性和同步性非常重要,需要外键支持,否则财务将会混乱,较为适合InnoDB。


二、字段类型选择


字段类型应该要满足需求,尽量要满足以下需求。

尽可能小(占用存储空间少)、尽可能定长(占用存储空间固定)、尽可能使用整数。
1.列类型之数值

(1)整型

MySQL数据库支持五种整型类型,包括:TINYINT、SMALLINT、MEDIUMINT、INT和BIGINT五种。


(2)浮点型(非精确)

MySQL数据库支持两种浮点类型:FLOAT(单精度)和DOUBLE(双精度)两种


(3)定点型(精确

浮点型由于内部的存储方式是数值,导致它在一定程度上取得的是近似值而非精确值。如果使用定点型,那么就可以精确取得小数部分,因为它内部存储方式是字符串形式。


2.列类型之日期

MySQL数据库中有五个可用的日期时间数据类型,分别为:DATE、DATETIME、TIME、YEAR、TIMESTAMP。


3.列类型之字符

字符集校对规则utf8_general_ci表示校对时不区分大小写,相对的cs表示区分大小写。还有一个bin结尾的是字节比较。而general是地区名,这里是通用,utf8表示编码。如果是gbk,可以使用gbk_chinese_ci,如果是utf8则用utf8_general。MySQL提供了多种对字符数据的存储类型,包括:CHAR、VARCHAR、VARBINARY、BLOB、TEXT、ENUM和SET等多种字符类型。

综上:短文本定长用char,变长用varchar,长文本用text。


三、范式与逆范式


为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

第一范式1NF,原子性

第二范式2NF,消除部分依赖

第三范式3NF,消除传递依赖


1、范式

(1)第一范式:具有原子性,确保每列保持原子性。
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式。

(2)第二范式:主键列与非主键列遵循完全函数依赖关系,确保表中的每列都和主键相关。

第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。

(3)第三范式:非主键列之间没有传递函数依赖关系索引,确保每列都和主键列直接相关,而不是间接相关。

所谓传递函数依赖,指的是如果存在A→B→C的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:
关键字段→非关键字段x→非关键字段y

比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。
先满足第一范式,再满足第二范式,才能满足第三范式。

2、逆范式

逆范式是指打破范式,通过增加冗余或重复的数据来提高数据库的性能。
示例:假如有一个商品表Goods:
字段有Goods_id(商品表), goods_name(商品名称), cat_id(所属类别的id)。
还有一个分类表Category:
字段有Cat_id(类别id), cat_name(类别名称)。
现在要查询类别id为3的商品的数量,例如分类列表查询:
分类ID 分类名称 商品数量


四、索引


1.索引概述

利用关键字,就是记录的部分数据(某个字段,某些字段,某个字段的一部分),建立与记录位置的对应关系,就是索引。索引的关键字一定是排序的。索引本质上是表字段的有序子集,它是提高查询速度最有效的方法。一个没有建立任何索引的表,就相当于一本没有目录的书,在每次查询时就会进行全表扫描,这样会导致查询效率极低、速度也极慢。如果建立索引,那么就好比一本添加的目录,通过目录的指引,迅速翻阅到指定的章节,提升的查询性能,节约了查询资源。


2.索引种类

从索引的定义方式和用途中来看:主键索引,唯一索引,普通索引,全文索引。

无论任何类型,都是通过建立关键字与位置的对应关系来实现的。索引是通过关键字找对应的记录的地址。

以上类型的差异:对索引关键字的要求不同。

关键字:记录的部分数据(某个字段,某些字段,某个字段的一部分)。

普通索引,index:对关键字没有要求。

唯一索引,unique index:要求关键字不能重复。同时增加唯一约束。

主键索引,primary key:要求关键字不能重复,也不能为NULL。同时增加主键约束。

全文索引,fulltext key:关键字的来源不是所有字段的数据,而是从字段中提取的特别关键词。


关键字含义:可以是某个字段,也可以是某些字段。如果一个索引通过在多个字段上提取的关键字,称之为复合索引。命令:alter table exp add index (field1, field2);
PS:这里主键索引和唯一索引的区别在于:主键索引不能为空值,唯一索引允许空值;主键索引在一张表内只能创建一个,唯一索引可以创建多个。主键索引肯定是唯一索引,但唯一索引不一定是主键索引。

3.索引原则
a、不要过度索引。索引越多,占用空间越大,反而性能变慢;
b.只对WHERE子句中频繁使用的建立索引;
c.尽可能使用唯一索引,重复值越少,索引效果越强;
d.使用短索引,如果char(255)太大,应该给它指定一个前缀长度,大部分情况下前10位或20位值基本是唯一的,那么就不要对整个列进行索引;
e.充分利用左前缀,这是针对复合索引,因为WHERE语句如果有AND并列,只能识别一个索引(获取记录最少的那个),索引需要使用复合索引,那么应该将WHERE最频繁的放置在左边。
f.索引存在,如果没有满足使用原则,也会导致索引无效:

4.索引的使用场景

5.前缀索引

6.全文索引

7.索引结构-b-tree介绍
Hash、B-Tree(B树)两种数据结构。指的是mysql存储索引所采用的数据结构。其中,用户所维护的所有的索引结构 B-Tree结构。
B-Tree的结构如下:
每个节点,存储多个关键字。关键字也会对应记录地址
以上设计为了解决一次性磁盘IO开销,可以读取到更多的关键字数量。

每个关键字之间,存在子节点指针:

如果是复合索引:

关键字的排序先排左侧字段,在左侧字段相同的情况下,再排序右侧字段:
8.聚集索引(聚簇索引)
B+Tree(B-Tree的变种)
在innodb的存储引擎上,主键索引是与数据记录存储在一起的(聚簇在一起的)。

带来的问题:

Innodb的其他索引,非主键索引(二级索引):
关键字对应的不再是记录的地址,而是记录的主键。


可见,检索需要二次检索。先检索到主键ID,再检索记录。

五、查询缓存query_cache

将select的结果,存取起来共二次使用的缓存区域:


MySQL提供的缓存区:
未开启前:

两次查询时间消耗一致。
开启查询缓存,通过变量控制:


开启并设置大小:


再次执行查询:


可见,第二次查询,使用了开启的缓存!
一旦开启查询缓存,MySQL会将所有可以被缓存的select语句都缓存。如果存在不想使用缓存的SQL执行,则可以使用 SQL_NO_CACHE语法提示达到目的:

注意:这里的缓存仅当数据表的记录改变时,缓存才会被删除。而不是依靠过期时间的。


六、分区分表


日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。

1.分区,partition,分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。

其实每个分区,就是独立的表。都要存储该分区数据的数据,索引等信息。
创建分区:在创建表时,指定分区的选项:
Create table table_name (定义)
Partition by 分区算法 (参数) 分区选项。
例如:Partition by key (id) partitions 5;

分区结果如下:myisam下

Innodb下

Tip:分区与存储引擎无关,是MySQL逻辑层完成的。


2.分表
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的子表名,然后去操作它。分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。(需要手动分表)
分表是分区之前用的,MYSQL5.1后,就开始用分区代替分表了。分表很少用了。

(1)水平分表
创建结构相同的N个表;


再创建用于管理学生ID的表student_id:(该表是为了提供自增的ID)

PHP客户端逻辑:

Merge,mrg_myisam


是MySQL提供一个可以将多个结构相同的myisam表,合并到一起的存储引擎:

(2)垂直分表
一张表中存在多个字段。这些字段可以分为常用字段和非常用字段,为了提高查表速度,我们可以把这两类字段分开来存储。主要目的,减少每条记录的长度。
通常我们按以下原则进行垂直拆分:把不常用的字段单独放在一张表;把text,blog等大字段拆分出来放在附表中;经常组合查询的列放在一张表中;

例如学生表可以分成:
基础表(Student_base)和额外表(Student_extra),两张表中记录为1:1的关系。
基础信息表Student_base
Id name age
额外信息表Student_extra
Id 籍贯 政治面貌

七、服务器架构介绍



服务器架构,不仅仅是用一台MySQL
主从复制:
Mysql服务器内部支持复制功能,仅仅需要通过配置完成下面的拓扑结构。一主多从典型结果:主服务器负责写数据。从服务器负责读数据。复制功能mysql会自带。

读写分离,负载均衡:
php不再操作MYSQL数据库服务器,而是去操作读写分离、负载均衡服务器,只要服务器安装了mysql proxy或Ameoba软件就可以实现读写分离和负载均衡,读写分离是指该服务器会判断客户端的操作是读还是写,从而选择操作mysql主服务器还是从服务器。负载均衡算法是指,客户端读操作时,该服务器会根据取余算法去选择一台从服务器。

上面的架构可以提升整体服务器的效率,高性能。
同时,服务器架构需要保证,高可用(稳定),7x24不宕机。因此需要增加一些冗余服务器以便备用。时时检测正在用的服务器。


八、SQL优化


1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

   
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
   
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
   
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
   
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
   
6.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
   
7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
   
8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
select id from t where name like 'abc%'
   
9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
   
10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
   
11.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
   
12.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
   
13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,
如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
   
14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
   
15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
   
16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,
其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
   
17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
   
18.避免频繁创建和删除临时表,以减少系统表资源的消耗。

19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。
但是,对于一次性事件,最好使用导出表。
   
20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,
以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,
然后 drop table ,这样可以避免系统表的较长时间锁定。
   
22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
   
23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

25.尽量避免大事务操作,提高系统并发能力。

26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。


九、慢查询日志的使用


定位执行较慢的查询语句方案。
show variables like 'slow_query%'; show variables like '%long_query%';

Slow_query_log = 0|1
Long_query_time = N 超过该时间临界点,就为慢查询。

开启日志
set global slow_query_log=1; set long_query_time=0.5;

执行SQL,查看:


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存